<!-- src/components/WaterfallList.vue -->
<template>
  <main ref="mainContainer" class="w-full flex-1 mx-auto overflow-y-auto">
    <!-- 瀑布流容器 -->
    <div v-if="props.items.length > 0" class="flex" :style="{ gap: `${props.gap}px` }">
      <!-- 动态渲染列 -->
      <div
        v-for="(column, colIndex) in columnsData"
        :key="`column-${colIndex}`"
        class="flex flex-col w-full"
        :style="{ gap: `${props.gap}px` }"
      >
        <!-- 渲染每列中的卡片 -->
        <!-- 使用 #default 插槽，将 item 数据传回给父组件 -->
        <slot v-for="item in column" :item="item" :key="props.getItemId(item)"></slot>
      </div>
    </div>

    <!-- 加载和状态指示器 -->
    <div class="h-20 flex items-center justify-center">
      <!-- 无限滚动触发器 -->
      <div ref="triggerEl"></div>

      <!-- 加载中 -->
      <div v-if="props.isLoading" class="text-center">
        <!-- 允许父组件通过插槽自定义加载动画 -->
        <slot name="loading">
          <p>Loading more...</p>
        </slot>
      </div>

      <!-- 全部加载完成 -->
      <div v-if="props.isFinished && props.items.length > 0" class="text-center text-gray-500">
        <slot name="finished">
          <p>You've reached the end.</p>
        </slot>
      </div>

      <!-- 无数据 -->
      <div v-if="!props.isLoading && props.items.length === 0 && props.isFinished" class="text-center text-gray-500">
         <slot name="empty">
           <p>No results found.</p>
         </slot>
      </div>
    </div>
  </main>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch, nextTick, type PropType } from 'vue';

// -------------------
// Props & Emits
// -------------------
const props = defineProps({
  // --- 数据源 ---
  items: { type: Array as PropType<any[]>, required: true },
  isLoading: { type: Boolean, default: false },
  isFinished: { type: Boolean, default: false },
  // --- 布局配置 ---
  colsMobile: { type: Number, default: 2 },
  colsDesktop: { type: Number, default: 5 },
  breakpoint: { type: Number, default: 768 },
  gap: { type: Number, default: 16 },
  // --- 数据访问器 (为了通用性) ---
  getItemId: {
    type: Function as PropType<(item: any) => string | number>,
    required: true
  },
  getAspectRatio: {
    type: Function as PropType<(item: any) => number>,
    required: true
  }
});

const emit = defineEmits(['loadMore']);

// -------------------
// DOM & 响应式引用
// -------------------
const mainContainer = ref<HTMLElement | null>(null);
const triggerEl = ref<HTMLElement | null>(null);
const screenWidth = ref(window.innerWidth);
const isCheckingAndFilling = ref(false);

// -------------------
// 核心计算属性
// -------------------
const updateScreenWidth = () => { screenWidth.value = window.innerWidth; };
const numColumns = computed(() => screenWidth.value < props.breakpoint ? props.colsMobile : props.colsDesktop);

const columnsData = computed(() => {
  const columns: { items: any[]; height: number }[] = Array.from(
    { length: numColumns.value },
    () => ({ items: [], height: 0 })
  );

  props.items.forEach(item => {
    // 找到当前最短的列
    const shortestColumn = columns.reduce((prev, curr) => curr.height < prev.height ? curr : prev);
    // 将项目添加到最短列
    shortestColumn.items.push(item);
    // 更新该列的高度 (基于宽高比)
    shortestColumn.height += 1 / props.getAspectRatio(item);
  });

  return columns.map(col => col.items);
});

// -------------------
// 无限滚动与屏幕填充逻辑
// -------------------

/**
 * 检查屏幕是否被内容填满，如果没有，则循环加载直到填满为止。
 */
const checkAndFillScreen = async () => {
  if (isCheckingAndFilling.value || props.isFinished) return;

  isCheckingAndFilling.value = true;

  // 使用 while 循环确保在加载后立即重新检查，直到屏幕被填满
  while (true) {
    await nextTick(); // 等待 DOM 更新

    if (!triggerEl.value || props.isFinished) {
      break; // 停止条件：元素不存在或已加载完毕
    }

    const rect = triggerEl.value.getBoundingClientRect();
    // 如果哨兵元素的顶部在视口之外，说明有滚动条，屏幕已满
    if (rect.top > window.innerHeight) {
      break;
    }
    
    // 如果仍然可见，并且没有在加载中，则请求更多数据
    if (!props.isLoading) {
      emit('loadMore');
    }
    
    // 等待下一次 props.isLoading 的变化来继续循环
    // 这里我们使用一个简单的延时来避免死循环，更好的方式是观察 isLoading
    // 但因为父组件会更新 isLoading，这个 watch 已经处理了循环的 "暂停"
    // 为了简单起见，我们只触发一次加载，然后让 watch 来重新触发检查
    break; // 触发一次加载后退出循环，让 watch 机制接管
  }

  isCheckingAndFilling.value = false;
};

const handleIntersect = (entries: IntersectionObserverEntry[]) => {
  // 当哨兵元素进入视口，并且我们没有在进行“填充屏幕”的操作，并且没有在加载中时，加载下一页
  if (entries[0].isIntersecting && !isCheckingAndFilling.value && !props.isLoading) {
    emit('loadMore');
  }
};

// 监听加载状态的变化。当任何一次加载完成时，都检查是否需要填充屏幕。
watch(() => props.isLoading, (loading) => {
  if (!loading && props.items.length > 0) {
    checkAndFillScreen();
  }
});

// 当列表项发生变化时（例如，新的搜索），也触发一次检查
watch(() => props.items, () => {
  // 确保在DOM更新后执行
  nextTick(() => {
    checkAndFillScreen();
  });
}, { deep: true });


// -------------------
// 生命周期钩子
// -------------------
let observer: IntersectionObserver | null = null;

onMounted(() => {
  window.addEventListener('resize', updateScreenWidth);

  if (mainContainer.value && triggerEl.value) {
    observer = new IntersectionObserver(handleIntersect, {
      root: null, // 观察整个视口
      threshold: 0.1
    });
    observer.observe(triggerEl.value);
  }
  
  // 初始检查
  checkAndFillScreen();
});

onUnmounted(() => {
  window.removeEventListener('resize', updateScreenWidth);
  if (observer) {
    observer.disconnect();
  }
});
</script>
